Skip to content

fix: Windows port conflict + HF_HUB_OFFLINE for offline model loading#85

Closed
caaaaaleb wants to merge 6 commits into
debpalash:mainfrom
caaaaaleb:main
Closed

fix: Windows port conflict + HF_HUB_OFFLINE for offline model loading#85
caaaaaleb wants to merge 6 commits into
debpalash:mainfrom
caaaaaleb:main

Conversation

@caaaaaleb

@caaaaaleb caaaaaleb commented May 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Three fixes to improve reliability on Windows and restricted networks:

1. Windows kill_orphan_on_port implementation

The Windows version was an empty function, causing stale backend processes to occupy the port. When a new backend spawned, uvicorn couldn't bind → shutdown → model loading continued in half-dead process → confusing "Could not import module AutoModel" error.

Fix: Implement kill_orphan_on_port for Windows using netstat -ano to find the listening PID and taskkill /PID /F to terminate it.

2. HF_HUB_OFFLINE=1 at import time

On restricted networks, lazy imports deep in the HuggingFace stack could trigger httpx.ConnectTimeout before local_files_only=True takes effect.

Fix: Set HF_HUB_OFFLINE=1 in main.py before ANY library import, and in model_manager._load_model_sync() with proper save/restore.

3. local_files_only=True for from_pretrained

Added local_files_only=True to OmniVoice.from_pretrained() call as belt-and-suspenders alongside HF_HUB_OFFLINE.

Tested

  • Backend starts and stays running on Windows
  • Model loads successfully via Tauri spawn
  • /system/info returns correct model checkpoint info
  • TTS generation works end-to-end

Summary by CodeRabbit

  • Documentation

    • README replaced with a Simplified Chinese, Windows-focused user guide: quick-start, install modes (MSI/source/dev), build/MSI notes, ports (3900/3901), FAQ and repo layout.
  • New Features

    • Full English + Simplified Chinese UI with persistent language toggle.
    • Windows start scripts for production and dev that run checks, launch services, and open the browser.
  • Bug Fixes

    • Offline model loading and Windows startup safeguards to avoid HF/Triton issues.
    • Auto-kill of orphaned Windows processes on occupied ports.
  • Chores

    • Dev server bound to 127.0.0.1; MSI build artifacts ignored.

Review Change Stack

- Implement kill_orphan_on_port for Windows using netstat + taskkill
- Set HF_HUB_OFFLINE=1 in main.py before any HF library import
- Set HF_HUB_OFFLINE=1 in model_manager._load_model_sync with restore
- Add local_files_only=True to OmniVoice.from_pretrained call

Fixes "Could not import module AutoModel" error caused by port
conflict when stale backend processes occupy the port on Windows.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 18, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 308d11a7-cfbd-4083-8359-e688f87963a4

📥 Commits

Reviewing files that changed from the base of the PR and between 095d305 and 8270aa6.

📒 Files selected for processing (1)
  • .gitignore
✅ Files skipped from review due to trivial changes (1)
  • .gitignore

📝 Walkthrough
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch main

- 全文翻译为中文
- 添加本 Fork 修复版说明(Windows 端口冲突、离线模式)
- 更新下载链接指向本 Fork Release
- 增加国内网络环境使用指南

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
README.md (2)

281-293: 💤 Low value

Add language identifier to fenced code block.

The architecture diagram code block should specify a language identifier to satisfy markdown linting rules. Consider using text or plaintext for ASCII art diagrams.

📝 Proposed fix
-```
+```text
 ┌─────────────────────────────────────────────────┐
 │                  前端 (React)                     │

As per coding guidelines, static analysis (markdownlint-cli2) reports: "Fenced code blocks should have a language specified (MD040)".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 281 - 293, The fenced ASCII-art diagram block (the
triple-backtick block containing the box with "前端 (React)" and "后端 (FastAPI)"
lines) needs a language identifier to satisfy MD040; update the opening fence
from ``` to ```text (or ```plaintext) so the block becomes a labeled code fence
(e.g., ```text) and keep the enclosed ASCII art unchanged.

8-9: 💤 Low value

Consider badge consistency with fork messaging.

The stars badge references the original repository (debpalash/OmniVoice-Studio) while the release badge references the fork (caaaaaleb/OmniVoice-Studio). This mixing could confuse users about which repository they're viewing.

Since this is a documented fork providing fixes, consider either:

  1. Using fork references for both badges, or
  2. Adding a note clarifying that stars reflect the upstream project
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 8 - 9, The README mixes upstream and fork badges
(stars link uses https://github.com/debpalash/OmniVoice-Studio while release
uses https://github.com/caaaaaleb/OmniVoice-Studio); make them consistent by
either updating the stars badge URL to the fork (change the href/src that
reference debpalash to caaaaaleb) or add a short clarifying sentence beneath the
badges stating that the star count reflects the upstream repository; ensure the
two anchor/img entries shown in the diff are updated together so both badges
point to the same repository or are accompanied by the clarification text.
frontend/src-tauri/src/backend.rs (1)

103-109: ⚡ Quick win

Surface cleanup failures in the logs.

This branch silently returns when netstat cannot be launched and ignores taskkill exit status. If cleanup fails because of permissions or a missing binary, we lose the only clue and fall back to the same bind error this PR is trying to fix.

Proposed change
     let out = match std::process::Command::new("netstat")
         .args(["-ano", "-p", "TCP"])
         .output()
     {
         Ok(o) => o,
-        Err(_) => return,
+        Err(e) => {
+            log::warn!("Failed to run netstat for port {}: {}", port, e);
+            return;
+        }
     };
+    if !out.status.success() {
+        log::warn!(
+            "netstat exited with status {:?} while probing port {}: {}",
+            out.status.code(),
+            port,
+            String::from_utf8_lossy(&out.stderr)
+        );
+        return;
+    }
...
-                let _ = std::process::Command::new("taskkill")
-                    .args(["/PID", &pid.to_string(), "/F"])
-                    .output();
+                match std::process::Command::new("taskkill")
+                    .args(["/PID", &pid.to_string(), "/F"])
+                    .output()
+                {
+                    Ok(kill) if !kill.status.success() => {
+                        log::warn!(
+                            "taskkill failed for pid {} on port {}: {}",
+                            pid,
+                            port,
+                            String::from_utf8_lossy(&kill.stderr)
+                        );
+                    }
+                    Err(e) => {
+                        log::warn!(
+                            "Failed to invoke taskkill for pid {} on port {}: {}",
+                            pid,
+                            port,
+                            e
+                        );
+                    }
+                    _ => {}
+                }

Also applies to: 135-137

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src-tauri/src/backend.rs` around lines 103 - 109, The code silently
returns when std::process::Command::new("netstat").output() fails and also
ignores the exit status of the subsequent taskkill invocation; update the Err(_)
arm of the Command::new("netstat")...output() match to log the actual error
(include the error from output()) instead of returning, and after
spawning/running taskkill check its ExitStatus and log a clear error if it
failed (include stdout/stderr if available) so permission/missing-binary
failures are visible; update the same pattern referenced around the other
invocation (lines near 135-137) to perform the same logging and non-silent
failure handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/src-tauri/src/backend.rs`:
- Around line 128-137: The current Windows branch blindly taskkills whatever PID
netstat reports; update the logic around the PID extraction (the block that
parses parts/ pid_str and produces pid and the taskkill call) to first query the
target process command line or executable and verify it belongs to our spawned
OmniVoice backend before killing: after parsing pid, run a safe metadata lookup
(e.g. via PowerShell/Get-Process or wmic for the given pid) to obtain
ProcessName/Path/CommandLine, compare that value against the expected backend
executable name or unique marker used when we spawn the backend, and only call
std::process::Command::new("taskkill") if the verification matches; otherwise
log a port-conflict error and surface the conflict instead of killing the
process.

---

Nitpick comments:
In `@frontend/src-tauri/src/backend.rs`:
- Around line 103-109: The code silently returns when
std::process::Command::new("netstat").output() fails and also ignores the exit
status of the subsequent taskkill invocation; update the Err(_) arm of the
Command::new("netstat")...output() match to log the actual error (include the
error from output()) instead of returning, and after spawning/running taskkill
check its ExitStatus and log a clear error if it failed (include stdout/stderr
if available) so permission/missing-binary failures are visible; update the same
pattern referenced around the other invocation (lines near 135-137) to perform
the same logging and non-silent failure handling.

In `@README.md`:
- Around line 281-293: The fenced ASCII-art diagram block (the triple-backtick
block containing the box with "前端 (React)" and "后端 (FastAPI)" lines) needs a
language identifier to satisfy MD040; update the opening fence from ``` to
```text (or ```plaintext) so the block becomes a labeled code fence (e.g.,
```text) and keep the enclosed ASCII art unchanged.
- Around line 8-9: The README mixes upstream and fork badges (stars link uses
https://github.com/debpalash/OmniVoice-Studio while release uses
https://github.com/caaaaaleb/OmniVoice-Studio); make them consistent by either
updating the stars badge URL to the fork (change the href/src that reference
debpalash to caaaaaleb) or add a short clarifying sentence beneath the badges
stating that the star count reflects the upstream repository; ensure the two
anchor/img entries shown in the diff are updated together so both badges point
to the same repository or are accompanied by the clarification text.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d3c53b90-30d8-4ff7-9193-1a53c28c786e

📥 Commits

Reviewing files that changed from the base of the PR and between e4dbf4c and 43b4d63.

📒 Files selected for processing (4)
  • README.md
  • backend/main.py
  • backend/services/model_manager.py
  • frontend/src-tauri/src/backend.rs

Comment on lines +128 to +137
let parts: Vec<&str> = line.split_whitespace().collect();
if let Some(pid_str) = parts.last() {
if let Ok(pid) = pid_str.parse::<u32>() {
log::warn!(
"Killing orphan process {} on port {} (Windows)",
pid, port
);
let _ = std::process::Command::new("taskkill")
.args(["/PID", &pid.to_string(), "/F"])
.output();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Don't taskkill arbitrary listeners on this port.

Line 135 force-kills whichever PID netstat reports, but this helper never verifies that the process is actually our backend. If some other local service is bound to the configured port, startup can terminate an unrelated app instead of surfacing a port conflict. Please check the process command line/executable matches the spawned OmniVoice backend before killing it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src-tauri/src/backend.rs` around lines 128 - 137, The current
Windows branch blindly taskkills whatever PID netstat reports; update the logic
around the PID extraction (the block that parses parts/ pid_str and produces pid
and the taskkill call) to first query the target process command line or
executable and verify it belongs to our spawned OmniVoice backend before
killing: after parsing pid, run a safe metadata lookup (e.g. via
PowerShell/Get-Process or wmic for the given pid) to obtain
ProcessName/Path/CommandLine, compare that value against the expected backend
executable name or unique marker used when we spawn the backend, and only call
std::process::Command::new("taskkill") if the verification matches; otherwise
log a port-conflict error and surface the conflict instead of killing the
process.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@README.md`:
- Around line 11-13: The directory tree lists both README.md and 使用指南.md but the
tree labels 使用指南.md as “本文件” while this PR edits README.md, causing confusion;
update the directory tree entry so the “本文件” label correctly refers to README.md
(or rename the file consistently) by changing the label for 使用指南.md to its
proper description and/or marking README.md as “本文件”, and ensure the README.md
and 使用指南.md filenames and their descriptive comments (in the README listing) are
consistent.
- Around line 9-25: The README.md's directory tree fenced code block is missing
a language tag (causing MD040); open the fenced block in README.md and add a
language tag (e.g., replace "```" with "```text") so the tree block is
recognized as plain text; locate the block shown in the diff (the directory tree
starting with "E:\OmniVoice-Studio\") and update its opening fence accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 17571d8b-401d-4d42-b46a-320f6c8d737c

📥 Commits

Reviewing files that changed from the base of the PR and between 43b4d63 and 16da0b0.

📒 Files selected for processing (1)
  • README.md

Comment thread README.md
Comment on lines 9 to 25
```

Open normally after. One-time fix.
</details>

<details>
<summary><b>Windows — first launch takes 5–10 minutes</b></summary>
<br/>

The app bootstraps a Python virtual environment, installs dependencies, and downloads ffmpeg on first run. The splash screen shows each step. Subsequent launches start in seconds.
</details>

<details>
<summary><b>Linux — AppImage needs FUSE</b></summary>
<br/>

If FUSE isn't available, use the `.deb` package or extract-and-run:

```bash
chmod +x OmniVoice.Studio_*.AppImage
./OmniVoice.Studio_*.AppImage --appimage-extract-and-run
```
</details>

<details>
<summary><b>Linux — White screen on Fedora 44 / Ubuntu 24.04</b></summary>
<br/>

Some newer distros ship a WebKit/GTK version with compositing issues. Try:

```bash
WEBKIT_DISABLE_COMPOSITING_MODE=1 ./OmniVoice.Studio_*.AppImage
E:\OmniVoice-Studio\
├── README.md # 项目说明(中文)
├── 使用指南.md # 本文件
├── OmniVoice_Studio_0.2.7_x64.msi # Windows 安装包 (~7 MB)
├── backend/ # Python 后端
│ ├── main.py # FastAPI 入口
│ └── services/
│ └── model_manager.py # 模型加载
├── frontend/ # React 前端 (Tauri)
│ └── src-tauri/
│ └── src/
│ └── backend.rs # 后端进程管理
├── omnivoice/ # OmniVoice Python 包
├── pyproject.toml # Python 项目配置
└── uv.lock # 依赖锁定文件
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a language tag to the fenced code block to satisfy markdownlint.

Line 9 opens a fenced block without a language, triggering MD040 and causing lint noise.

Suggested patch
-```
+```text
 E:\OmniVoice-Studio\
 ├── README.md                              # 项目说明(中文)
 ...
 └── uv.lock                                 # 依赖锁定文件
</details>

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.22.1)</summary>

[warning] 9-9: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @README.md around lines 9 - 25, The README.md's directory tree fenced code
block is missing a language tag (causing MD040); open the fenced block in
README.md and add a language tag (e.g., replace "" with "text") so the
tree block is recognized as plain text; locate the block shown in the diff (the
directory tree starting with "E:\OmniVoice-Studio") and update its opening
fence accordingly.


</details>

<!-- fingerprinting:phantom:triton:hawk -->

<!-- This is an auto-generated comment by CodeRabbit -->

Comment thread README.md Outdated
- Add complete zh/en i18n across 50+ source files with i18next
- Add language switcher (中/EN toggle) in header with localStorage persistence
- Fix _gpu_pool NameError in model_manager.py (__getattr__ internal access)
- Fix TritonMissing on Windows: disable torch.compile/dynamo/inductor via env vars + explicit check
- Fix Vite IPv6-only binding: force host to 127.0.0.1
- Add start.bat (production, .venv only) and start-dev.bat (Vite HMR)
- Rebuild frontend dist with all fixes
- Update README with new features and fix table

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
start.bat (1)

1-66: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: Convert file to Windows line endings (CRLF).

The file uses Unix line endings (LF-only), which will cause parsing failures on Windows due to CMD.exe parser bugs. Batch files require CRLF line endings to work reliably.

🔧 How to fix

Convert the file to Windows line endings using one of:

Git:

# Configure git to auto-convert on checkout (run once in repo root)
git config core.autocrlf true
git rm --cached start.bat
git reset --hard

Command line:

# Using unix2dos (if available)
unix2dos start.bat

# Or using PowerShell
(Get-Content start.bat) | Set-Content start.bat

Editor:

  • VS Code: Click "LF" in status bar → select "CRLF"
  • Notepad++: Edit → EOL Conversion → Windows (CR LF)

As per coding guidelines, every fix must work on Windows (x64) with no platform-only regressions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@start.bat` around lines 1 - 66, The start.bat file currently uses LF-only
line endings which will break CMD parsing; convert start.bat to Windows CRLF
endings (ensure every line ends with CRLF) and commit that change; you can do
this by enabling Git autocrlf and re-checking out the file (git config
core.autocrlf true; git rm --cached start.bat; git reset --hard), or run a
conversion tool (unix2dos start.bat) or change the EOL in your editor (e.g., VS
Code: click LF → select CRLF), then verify the batch commands like the for
loops, start "OmniVoice-Backend", curl health check and taskkill lines run
correctly on Windows before committing.
start-dev.bat (1)

1-89: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: Convert file to Windows line endings (CRLF).

The file uses Unix line endings (LF-only), which will cause parsing failures on Windows due to CMD.exe parser bugs. Batch files require CRLF line endings to work reliably.

🔧 How to fix

Convert the file to Windows line endings using one of:

Git:

# Configure git to auto-convert on checkout (run once in repo root)
git config core.autocrlf true
git rm --cached start-dev.bat
git reset --hard

Command line:

# Using unix2dos (if available)
unix2dos start-dev.bat

# Or using PowerShell
(Get-Content start-dev.bat) | Set-Content start-dev.bat

Editor:

  • VS Code: Click "LF" in status bar → select "CRLF"
  • Notepad++: Edit → EOL Conversion → Windows (CR LF)

As per coding guidelines, every fix must work on Windows (x64) with no platform-only regressions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@start-dev.bat` around lines 1 - 89, The batch script uses LF-only line
endings which break CMD parsing; convert start-dev.bat to Windows CRLF line
endings and commit the change so Windows can execute it reliably. Fix by
converting the file (e.g., run unix2dos on start-dev.bat, use PowerShell
Set-Content to rewrite with CRLF, enable git core.autocrlf and refresh the file,
or change EOL to CRLF in your editor/VS Code) and verify the top of the script
(starts with "`@echo` off") runs successfully on a Windows x64 environment before
pushing.
frontend/src/pages/Projects.jsx (1)

163-176: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Variable shadowing causes runtime error.

The loop variable t at line 163 shadows the translation function t from useTranslation() declared at line 90. When line 167 attempts to call t('projects.transcription_item'), it will fail with TypeError: t is not a function because t now refers to the current transcription object, not the translation function.

🐛 Proposed fix: rename loop variable
-    for (const t of transcriptions) {
+    for (const trans of transcriptions) {
       list.push({
         type: 'transcripts',
-        id: t.id || String(Math.random()),
-        title: (t.text || t('projects.transcription_item')).slice(0, 120),
-        subtitle: [t.language, t.duration_s ? `${Math.round(t.duration_s)}s` : ''].filter(Boolean).join(' · '),
-        ts: t.timestamp ? Date.parse(t.timestamp) : 0,
+        id: trans.id || String(Math.random()),
+        title: (trans.text || t('projects.transcription_item')).slice(0, 120),
+        subtitle: [trans.language, trans.duration_s ? `${Math.round(trans.duration_s)}s` : ''].filter(Boolean).join(' · '),
+        ts: trans.timestamp ? Date.parse(trans.timestamp) : 0,
         accent: '`#83a598`',
         Icon: FileText,
         onClick: () => {
-          navigator.clipboard.writeText(t.text || '');
+          navigator.clipboard.writeText(trans.text || '');
         },
       });
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/Projects.jsx` around lines 163 - 176, The loop is
shadowing the translation function t from useTranslation() by using `for (const
t of transcriptions)`, causing `t('projects.transcription_item')` to fail;
rename the loop variable (e.g., `for (const transcript of transcriptions)` or
`tr`) and update all references inside the loop (`t.id`, `t.text`, `t.language`,
`t.duration_s`, `t.timestamp`) to the new name so the translation function `t`
remains callable.
🧹 Nitpick comments (2)
frontend/src/components/ExportModal.jsx (1)

76-80: 💤 Low value

Consider adding language to useMemo dependencies.

The useMemo uses t('dub.original') but only depends on dubTracks. While the component will re-render when language changes (via react-i18next context), explicitly tracking language in dependencies makes the intent clearer and satisfies ESLint exhaustive-deps.

📦 Suggested addition to dependencies
+import { useTranslation } from 'react-i18next';
+
 export default function ExportModal({
   ...
 }) {
   const { t, i18n } = useTranslation();
   ...
   const allTracks = useMemo(() => {
     const out = [{ code: 'original', label: t('dub.original'), kind: 'original' }];
     (dubTracks || []).forEach(t => out.push({ code: t, label: t.toUpperCase(), kind: 'dub' }));
     return out;
-  }, [dubTracks]);
+  }, [dubTracks, i18n.language, t]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/ExportModal.jsx` around lines 76 - 80, The memo for
allTracks uses the i18n translator t('dub.original') but only lists dubTracks in
its dependency array; update the useMemo dependencies for the allTracks constant
(the useMemo call that builds out from dubTracks) to include the
translator/language so the memo updates when language changes—e.g., add either
the t function or the current i18n.language value to the dependency array to
satisfy exhaustive-deps and ensure labels update on locale change.
frontend/src/components/GlossaryPanel.jsx (1)

175-182: 💤 Low value

Consider renaming map parameter to avoid shadowing.

The loop variable t shadows the translation function from useTranslation(). While this doesn't cause a runtime error (since the translation function isn't called inside the map), it reduces code clarity and could confuse maintainers.

♻️ Suggested refactor
-              {terms.map(t => (
+              {terms.map(term => (
                 <GlossaryRow
-                  key={t.id}
-                  term={t}
-                  onUpdate={(patch) => onUpdate(t.id, patch)}
-                  onDelete={() => onDelete(t.id)}
+                  key={term.id}
+                  term={term}
+                  onUpdate={(patch) => onUpdate(term.id, patch)}
+                  onDelete={() => onDelete(term.id)}
                 />
               ))}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/GlossaryPanel.jsx` around lines 175 - 182, Rename the
map loop parameter that currently uses "t" to avoid shadowing the translation
function from useTranslation(); for example, change the iterator in the
terms.map callback to "term" or "termItem" and update all references inside the
callback (key, term prop, onUpdate/onDelete closures) so GlossaryRow receives
the same data but no longer conflicts with the translation function name.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/src/App.jsx`:
- Around line 216-218: The default compareText initializer references an
undefined i18n instead of using the translation hook; inside the App component
call useTranslation() (e.g., const { t } = useTranslation()) and update the
useState initializer for compareText to use t('voice.default_test_phrase') (with
a try/catch or fallback string) so the localized default is used; update any
usages that referenced i18n.t to use the destructured t function and ensure
useTranslation is imported and invoked within the App component scope.

In `@frontend/src/pages/CloneDesignTab.jsx`:
- Line 211: The concatenation at the JSX expression using
t('voice.using_profile') + profiles.find(p => p.id === selectedProfile)?.name
breaks localization; instead pass the profile name as an interpolation variable
to the translator. Replace the concatenated expression with a single t call that
supplies the name (e.g., t('voice.using_profile', { name: profiles.find(p =>
p.id === selectedProfile)?.name })) so translators control spacing/word order;
update the JSX where selectedProfile and profiles are referenced accordingly.

In `@frontend/src/pages/Transcriptions.jsx`:
- Around line 76-82: The useCallback hooks for copyText and exportAll capture
the translation function t but do not include it in their dependency arrays,
causing stale translations after language changes; update the useCallback
declarations for copyText and exportAll to include t (e.g., useCallback(..., [t,
...otherDeps])) so they re-create with the latest translator when language
switches.

---

Outside diff comments:
In `@frontend/src/pages/Projects.jsx`:
- Around line 163-176: The loop is shadowing the translation function t from
useTranslation() by using `for (const t of transcriptions)`, causing
`t('projects.transcription_item')` to fail; rename the loop variable (e.g., `for
(const transcript of transcriptions)` or `tr`) and update all references inside
the loop (`t.id`, `t.text`, `t.language`, `t.duration_s`, `t.timestamp`) to the
new name so the translation function `t` remains callable.

In `@start-dev.bat`:
- Around line 1-89: The batch script uses LF-only line endings which break CMD
parsing; convert start-dev.bat to Windows CRLF line endings and commit the
change so Windows can execute it reliably. Fix by converting the file (e.g., run
unix2dos on start-dev.bat, use PowerShell Set-Content to rewrite with CRLF,
enable git core.autocrlf and refresh the file, or change EOL to CRLF in your
editor/VS Code) and verify the top of the script (starts with "`@echo` off") runs
successfully on a Windows x64 environment before pushing.

In `@start.bat`:
- Around line 1-66: The start.bat file currently uses LF-only line endings which
will break CMD parsing; convert start.bat to Windows CRLF endings (ensure every
line ends with CRLF) and commit that change; you can do this by enabling Git
autocrlf and re-checking out the file (git config core.autocrlf true; git rm
--cached start.bat; git reset --hard), or run a conversion tool (unix2dos
start.bat) or change the EOL in your editor (e.g., VS Code: click LF → select
CRLF), then verify the batch commands like the for loops, start
"OmniVoice-Backend", curl health check and taskkill lines run correctly on
Windows before committing.

---

Nitpick comments:
In `@frontend/src/components/ExportModal.jsx`:
- Around line 76-80: The memo for allTracks uses the i18n translator
t('dub.original') but only lists dubTracks in its dependency array; update the
useMemo dependencies for the allTracks constant (the useMemo call that builds
out from dubTracks) to include the translator/language so the memo updates when
language changes—e.g., add either the t function or the current i18n.language
value to the dependency array to satisfy exhaustive-deps and ensure labels
update on locale change.

In `@frontend/src/components/GlossaryPanel.jsx`:
- Around line 175-182: Rename the map loop parameter that currently uses "t" to
avoid shadowing the translation function from useTranslation(); for example,
change the iterator in the terms.map callback to "term" or "termItem" and update
all references inside the callback (key, term prop, onUpdate/onDelete closures)
so GlossaryRow receives the same data but no longer conflicts with the
translation function name.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a747c065-c8fb-4fe7-8464-b682031eb714

📥 Commits

Reviewing files that changed from the base of the PR and between 16da0b0 and 81789e3.

📒 Files selected for processing (56)
  • .gitignore
  • README.md
  • backend/main.py
  • backend/services/model_manager.py
  • frontend/src/App.jsx
  • frontend/src/components/AudioTrimmer.jsx
  • frontend/src/components/BatchAddDialog.jsx
  • frontend/src/components/BootstrapSplash.jsx
  • frontend/src/components/CaptureWidget.jsx
  • frontend/src/components/CastingView.jsx
  • frontend/src/components/CheckpointBanner.jsx
  • frontend/src/components/CompareModal.jsx
  • frontend/src/components/DirectionDialog.jsx
  • frontend/src/components/DubSegmentRow.jsx
  • frontend/src/components/DubSegmentTable.jsx
  • frontend/src/components/ErrorBoundary.jsx
  • frontend/src/components/ExportModal.jsx
  • frontend/src/components/FloatingPill.jsx
  • frontend/src/components/GlossaryPanel.jsx
  • frontend/src/components/Header.jsx
  • frontend/src/components/KeyboardCheatsheet.jsx
  • frontend/src/components/LogsFooter.jsx
  • frontend/src/components/MultiLangPicker.jsx
  • frontend/src/components/NavRail.jsx
  • frontend/src/components/NotificationPanel.jsx
  • frontend/src/components/ReadinessChecklist.jsx
  • frontend/src/components/SearchableSelect.jsx
  • frontend/src/components/Sidebar.jsx
  • frontend/src/components/StoriesEditor.jsx
  • frontend/src/components/VoicePreview.jsx
  • frontend/src/components/WaveformTimeline.jsx
  • frontend/src/hooks/useAppData.js
  • frontend/src/hooks/useDubWorkflow.js
  • frontend/src/hooks/useProfiles.js
  • frontend/src/hooks/useTTS.js
  • frontend/src/i18n/index.ts
  • frontend/src/i18n/locales/en.json
  • frontend/src/i18n/locales/zh.json
  • frontend/src/main-app.jsx
  • frontend/src/pages/BatchQueue.jsx
  • frontend/src/pages/CloneDesignTab.jsx
  • frontend/src/pages/DonatePage.jsx
  • frontend/src/pages/DubTab.jsx
  • frontend/src/pages/EnterprisePage.jsx
  • frontend/src/pages/Launchpad.jsx
  • frontend/src/pages/Projects.jsx
  • frontend/src/pages/Settings.jsx
  • frontend/src/pages/SetupWizard.jsx
  • frontend/src/pages/ToolsPage.jsx
  • frontend/src/pages/Transcriptions.jsx
  • frontend/src/pages/VoiceGallery.jsx
  • frontend/src/pages/VoiceProfile.jsx
  • frontend/src/ui/Dialog.jsx
  • frontend/vite.config.js
  • start-dev.bat
  • start.bat
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/main.py

Comment thread frontend/src/App.jsx
Comment on lines +216 to +218
const [compareText, setCompareText] = useState(() => {
try { return i18n?.t?.('voice.default_test_phrase'); } catch { return "The quick brown fox jumps over the lazy dog."; }
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -nP '\bi18n\?\.t\?\(' frontend/src/App.jsx
rg -nP '^import .*\\bi18n\\b' frontend/src/App.jsx

Repository: debpalash/OmniVoice-Studio

Length of output: 52


🏁 Script executed:

#!/bin/bash
# Check if file exists and get line count
ls -la frontend/src/App.jsx 2>/dev/null || echo "File not found at frontend/src/App.jsx"
wc -l frontend/src/App.jsx 2>/dev/null || echo "Cannot count lines"

Repository: debpalash/OmniVoice-Studio

Length of output: 167


🏁 Script executed:

#!/bin/bash
# Read lines 216-218 and surrounding context
if [ -f frontend/src/App.jsx ]; then
  echo "=== Lines 210-225 ===" 
  sed -n '210,225p' frontend/src/App.jsx | cat -n
fi

Repository: debpalash/OmniVoice-Studio

Length of output: 985


🏁 Script executed:

#!/bin/bash
# Search for import statements related to i18n, translation, or useTranslation
if [ -f frontend/src/App.jsx ]; then
  echo "=== Import statements for i18n/translation ===" 
  head -50 frontend/src/App.jsx | grep -nE 'import.*i18n|import.*useTranslation|import.*translation'
fi

Repository: debpalash/OmniVoice-Studio

Length of output: 167


🏁 Script executed:

#!/bin/bash
# Check for all imports at the top of the file
if [ -f frontend/src/App.jsx ]; then
  echo "=== First 60 lines (imports section) ===" 
  head -60 frontend/src/App.jsx | cat -n
fi

Repository: debpalash/OmniVoice-Studio

Length of output: 3581


🏁 Script executed:

#!/bin/bash
# Search for useTranslation usage in App.jsx
rg -n 'useTranslation\|const \{ t \}' frontend/src/App.jsx | head -20

Repository: debpalash/OmniVoice-Studio

Length of output: 52


Undefined i18n reference prevents localized default compare text.

Line 217 references i18n without a binding. The useTranslation hook is imported but never instantiated in the App component, so the i18n object doesn't exist and the try/catch always falls back to English text.

Call useTranslation() to destructure the t function and use it directly:

Suggested fix
+  const { t } = useTranslation();
+
   const [compareText, setCompareText] = useState(() => {
-    try { return i18n?.t?.('voice.default_test_phrase'); } catch { return "The quick brown fox jumps over the lazy dog."; }
+    return t('voice.default_test_phrase', { defaultValue: 'The quick brown fox jumps over the lazy dog.' });
   });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [compareText, setCompareText] = useState(() => {
try { return i18n?.t?.('voice.default_test_phrase'); } catch { return "The quick brown fox jumps over the lazy dog."; }
});
const { t } = useTranslation();
const [compareText, setCompareText] = useState(() => {
return t('voice.default_test_phrase', { defaultValue: 'The quick brown fox jumps over the lazy dog.' });
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/App.jsx` around lines 216 - 218, The default compareText
initializer references an undefined i18n instead of using the translation hook;
inside the App component call useTranslation() (e.g., const { t } =
useTranslation()) and update the useState initializer for compareText to use
t('voice.default_test_phrase') (with a try/catch or fallback string) so the
localized default is used; update any usages that referenced i18n.t to use the
destructured t function and ensure useTranslation is imported and invoked within
the App component scope.

<div className="clone-profile-banner">
<span className="clone-profile-banner__label">
Using profile: {profiles.find(p => p.id === selectedProfile)?.name}
{t('voice.using_profile')}{profiles.find(p => p.id === selectedProfile)?.name}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use interpolation for the selected-profile label.

Line 211 concatenates translated text with the profile name, which breaks spacing/word order in some locales. Pass name through t(...) interpolation instead.

Proposed fix
-                  {t('voice.using_profile')}{profiles.find(p => p.id === selectedProfile)?.name}
+                  {t('voice.using_profile', {
+                    name: profiles.find(p => p.id === selectedProfile)?.name ?? '',
+                  })}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{t('voice.using_profile')}{profiles.find(p => p.id === selectedProfile)?.name}
{t('voice.using_profile', {
name: profiles.find(p => p.id === selectedProfile)?.name ?? '',
})}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/CloneDesignTab.jsx` at line 211, The concatenation at the
JSX expression using t('voice.using_profile') + profiles.find(p => p.id ===
selectedProfile)?.name breaks localization; instead pass the profile name as an
interpolation variable to the translator. Replace the concatenated expression
with a single t call that supplies the name (e.g., t('voice.using_profile', {
name: profiles.find(p => p.id === selectedProfile)?.name })) so translators
control spacing/word order; update the JSX where selectedProfile and profiles
are referenced accordingly.

Comment on lines 76 to 82
const copyText = useCallback((text) => {
navigator.clipboard.writeText(text).then(
() => toast.success('Copied to clipboard'),
() => toast.error('Copy failed')
() => toast.success(t('transcriptions.copied_to_clipboard')),
() => toast.error(t('transcriptions.copy_failed'))
);
}, []);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify callback dependency arrays around translated toasts.
rg -n -C2 "const copyText = useCallback|const exportAll = useCallback|toast\\.success\\(|toast\\.error\\(" frontend/src/pages/Transcriptions.jsx

Repository: debpalash/OmniVoice-Studio

Length of output: 681


Add t to callback dependency arrays to prevent stale translations after language switch.

The copyText (line 76) and exportAll (line 96) callbacks use the t function but omit it from their dependency arrays. This causes the callbacks to retain old translations when the language changes at runtime.

🔧 Proposed fix
   const copyText = useCallback((text) => {
     navigator.clipboard.writeText(text).then(
       () => toast.success(t('transcriptions.copied_to_clipboard')),
       () => toast.error(t('transcriptions.copy_failed'))
     );
-  }, []);
+  }, [t]);
@@
   const exportAll = useCallback(() => {
@@
     URL.revokeObjectURL(url);
     toast.success(t('transcriptions.exported_transcriptions'));
-  }, [transcriptions]);
+  }, [transcriptions, t]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/Transcriptions.jsx` around lines 76 - 82, The useCallback
hooks for copyText and exportAll capture the translation function t but do not
include it in their dependency arrays, causing stale translations after language
changes; update the useCallback declarations for copyText and exportAll to
include t (e.g., useCallback(..., [t, ...otherDeps])) so they re-create with the
latest translator when language switches.

@debpalash

Copy link
Copy Markdown
Owner

Thanks @caaaaaleb — the Windows kill_orphan_on_port implementation and the HF_HUB_OFFLINE / local_files_only belt-and-suspenders are both real fixes we want, and the _gpu_pool_get_gpu_pool() catch is excellent work (the module-level __getattr__ shim was breaking under PyInstaller).

However the PR also rewrites the README into Simplified Chinese, flips the i18n fallbackLng from en to zh, and ships ~3,500 lines of UI string refactoring across 40+ JSX files — those changes affect every user globally and need their own review.

Could you split this into:

  1. The four backend/Rust bugfixesbackend/main.py, backend/services/model_manager.py, frontend/src-tauri/src/backend.rs Windows kill_orphan, and the _get_gpu_pool fix. We'll merge this quickly.
  2. i18n workzh.json (+1086 lines), the 40+ JSX t() wrappings, and README.zh-CN.md added alongside the existing English README (not replacing it). Keep fallbackLng: 'en' so non-Chinese users don't get Chinese fallback text.
  3. start.bat / start-dev.bat scripts as a third PR — the taskkill /f against any PID on :3900 is a useful affordance but should be audited (a user running another service on that port loses it without confirmation).

One small nit on (1): in the Windows kill_orphan_on_port netstat parser, the :PORT needle with trailing space is built but never actually matched against — falls through to port_suffix for every line. Simplify to a single local_addr.ends_with(&format!(":{}", port)) check.

Coordination: The HF_HUB_OFFLINE=1 work in main.py is complementary to our Phase 3 mirror-cascade plan (no conflict). The Windows kill_orphan fix unblocks Windows users today and can land independently. The vite host: '127.0.0.1' change parallels PR #84's bind-address audit — fine, but worth calling out.

Happy to walk through the split if helpful. Looking forward to landing the backend fixes — they're solid work.

feat: voice design personality presets with gender/age/pitch/accent attrs

- Add structured attrs (Gender, Age, Pitch, Style, EnglishAccent,
  ChineseDialect) to all 6 personality presets in personalities.py,
  giving each a distinct voice profile instead of relying solely on
  the free-text instruct field.
- CloneDesignTab: apply personality attrs via setVdStates when
  selecting a preset, replacing the bare setInstruct call.
- useTTS: remove the redundant instruct.trim() append to the prompt
  parts — attrs now drive the voice-design parameters, and including
  the raw instruct string was double-counting what attrs already
  express.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/src/pages/CloneDesignTab.jsx (1)

61-68: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restore instruct application when applying a personality.

At Line 67, only p.attrs is applied. p.instruct is no longer applied, so personality presets are only partially reflected in the UI state.

Proposed fix
 const applyPersonality = (p) => {
   if (activePersonality === p.id) {
     setActivePersonality('');
     return;
   }
   setActivePersonality(p.id);
-  if (p.attrs) setVdStates(p.attrs);
+  if (p.attrs) setVdStates(prev => ({ ...prev, ...p.attrs }));
+  if (p.instruct) setInstruct(p.instruct);
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/CloneDesignTab.jsx` around lines 61 - 68, applyPersonality
currently only applies p.attrs via setVdStates and therefore doesn't restore the
personality's instruct text; update applyPersonality to also apply p.instruct
when present (e.g., call the component state updater that holds the instruct
text, such as setInstruct(p.instruct)) when activating a personality, while
leaving the existing behavior for deactivation (setActivePersonality(''))
intact; ensure you check p.instruct is defined before setting it so you don't
overwrite existing instruct state with undefined.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@frontend/src/pages/CloneDesignTab.jsx`:
- Around line 61-68: applyPersonality currently only applies p.attrs via
setVdStates and therefore doesn't restore the personality's instruct text;
update applyPersonality to also apply p.instruct when present (e.g., call the
component state updater that holds the instruct text, such as
setInstruct(p.instruct)) when activating a personality, while leaving the
existing behavior for deactivation (setActivePersonality('')) intact; ensure you
check p.instruct is defined before setting it so you don't overwrite existing
instruct state with undefined.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1594e2e8-07d8-4cb8-a181-83347846c43d

📥 Commits

Reviewing files that changed from the base of the PR and between 81789e3 and 095d305.

📒 Files selected for processing (3)
  • backend/core/personalities.py
  • frontend/src/hooks/useTTS.js
  • frontend/src/pages/CloneDesignTab.jsx
💤 Files with no reviewable changes (1)
  • frontend/src/hooks/useTTS.js

chore: add models_cache/ to .gitignore

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@
@debpalash

Copy link
Copy Markdown
Owner

Triage (v0.3.0 line): this PR is currently conflicting with main (58 files, +4006/-1861) and overlaps the active plan-02 (Windows runtime integrity) and plan-03 (installer network resilience) work. Two of its themes are being addressed root-cause-first there:

Could you rebase onto current main and split it down to just the parts not already covered by those plans? A focused, rebased PR is much easier to absorb than the current broad diff. Flagging rather than closing — keen to keep the Windows fixes that aren't duplicated.

@debpalash

Copy link
Copy Markdown
Owner

The three fixes here are genuinely valuable — the Windows kill_orphan_on_port implementation and HF_HUB_OFFLINE offline loading directly serve the "first-run that actually works on Windows / restricted networks" goal.

The problem is scope: this PR touches 58 files / +4006 / −1861, but ~50 of those are whole-frontend reformatting churn (App.jsx, AudioTrimmer, every component) unrelated to the fixes. That looks like fork drift / an editor reformat. We can't review or land that safely — it'd conflict heavily with main and risks regressing recently-shipped work.

Could you scope it down to just the fix files? Based on the diff that's roughly:

  • frontend/src-tauri/src/backend.rs — Windows kill_orphan_on_port (netstat -ano)
  • backend/main.py + backend/services/model_manager.pyHF_HUB_OFFLINE offline loading
  • backend/core/personalities.py (if part of the fix)

A clean branch off current main with only those changes (and no blanket reformat) merges easily. If it's faster for you, I'm happy to re-implement the Windows port-conflict + offline-loading fixes directly with you credited as reporter — just confirm which path you prefer. Either way we want these in v0.3.0.

@debpalash

Copy link
Copy Markdown
Owner

Thanks so much for this, @caaaaaleb — the Windows fixes here are genuinely valuable and we want them in. 🙏

We've salvaged the two safe, high-value pieces into a clean PR (#156), with you credited as co-author on the commit:

  • The Windows kill_orphan_on_port implementation (netstat -anotaskkill) — this was a no-op before, so it's a real fix.
  • The Windows Triton / torch.compile disable in backend/main.py (guarded to win32, using setdefault so it never overrides a user-set value).

A few things we intentionally didn't carry over, and why:

  • The forced HF_HUB_OFFLINE="1" + local_files_only=True — this is the main reason we couldn't take the PR as-is. It would break first-run model downloads for every user, and downloading models on first use is core to how the app works. Offline mode needs to stay opt-in (only when a user sets HF_HUB_OFFLINE themselves). If you've been hitting httpx connect timeouts on a restricted network, that's a real problem we'd love to solve properly — likely via an opt-in offline toggle and/or the uv/HF mirror fallbacks we're already planning — so any detail on your setup would help.
  • The model_manager.py torch.compile / gpu-pool changesmain already has an equivalent (and a bit more capable) version of these: should_torch_compile() gates on Triton availability and falls back to eager on Windows, and the lazy _get_gpu_pool() is already in place. So your goal there is already covered.
  • The README rewrite and the frontend .jsx formatting churn — out of scope for a Windows-fix change; keeping the diff focused makes it much easier to review and ship.

We're leaving this PR open for you / a maintainer to close at your discretion. Thank you again for the contribution — the port-conflict and Triton fixes are landing because of you.

debpalash added a commit that referenced this pull request May 30, 2026
…aged from #85) (#156)

* fix(windows): port-conflict kill + Triton/torch.compile disable (salvaged from #85)

Salvages the two safe, valuable Windows fixes from community PR #85
without the changes that would regress all users.

backend.rs — implement the Windows branch of `kill_orphan_on_port`,
which was a no-op (`pub fn kill_orphan_on_port(_port: u16) {}`). It now
parses `netstat -ano -p TCP` for the LISTENING socket on exactly `port`
(suffix-matched on ":PORT" to avoid e.g. :3900 matching 39000) and kills
the owning PID via `taskkill /PID <pid> /F`. Behind `#[cfg(not(unix))]`;
the unix branch is untouched. Signature now matches the unix branch.

main.py — on Windows (sys.platform == "win32"), default
TORCH_COMPILE_DISABLE / TORCHDYNAMO_DISABLE / TORCHINDUCTOR_DISABLE to
"1" before torch is imported. Triton has no Windows wheel, so these
prevent TritonMissing/dynamo errors. Uses os.environ.setdefault (never
overrides an explicit user value) and is win32-guarded, so cross-platform
default behavior is unchanged.

Intentionally NOT salvaged from #85:
- The unconditional `os.environ["HF_HUB_OFFLINE"] = "1"` in main.py and
  the `local_files_only=True` / HF_HUB_OFFLINE save-restore in
  model_manager.py. This breaks first-run model downloads for every user
  (downloading models on first use is the core value prop). Offline mode
  must stay opt-in — only when the user sets HF_HUB_OFFLINE themselves.
- The model_manager.py torch.compile/_get_gpu_pool changes: already
  present on main in a superior form (`should_torch_compile()` gating
  from plan-02/#65 and the existing `_get_gpu_pool()` lazy pool), so
  applying #85's cruder `TORCH_COMPILE_DISABLE` env check would regress.
- The 512-line README rewrite, the ~50 frontend .jsx formatting-only
  diffs, and the personalities.py attrs additions (out of scope).

Refs #85.

Co-Authored-By: caaaaaleb <caaaaaleb@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: move win32 torch-disable block below sys.path preamble

test_main_py_bootstrap_adds_backend_dir asserts the first 15 lines of
main.py contain sys.path.insert + _backend_dir. The win32 block was placed
above the preamble, pushing them out of range. The torch env vars only need
to precede torch's (lazy) import, so moving the block below the sys.path
bootstrap keeps behavior identical and restores the test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: caaaaaleb <caaaaaleb@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@debpalash

Copy link
Copy Markdown
Owner

Your Windows fixes shipped via #156 (merged) — the netstat-based kill_orphan_on_port and the win32 Triton/torch.compile disable, credited to you. We intentionally did not take the unconditional HF_HUB_OFFLINE=1/local_files_only=True (it breaks first-run model downloads for everyone — offline should stay opt-in) nor the README rewrite/formatting churn. Thanks @caaaaaleb — closing as superseded by #156.

@debpalash debpalash closed this May 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants